/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package graphviewer;

import service.*;
import java.util.*;

/**
 *
 * @author Sabitov Artem
 * @since 21.09.2013
 */
public class GraphCalculate {
	public static  double MIN_DISTANCE_WITHOUT_EDGE = 120;//160;// минимальное расстояние на котором перестает действовать сила отталкивания между нодами без ребер
	public static  double MIN_DISTANCE_ATTRACTIVE = 110;	// минимальное расстояние на котором действует притяжение
	public static  double MAX_DISTANCE_REPULSIVE = 80;	// максимальное расстояние на котором действует отталкивание
	public static  double FORCE_FACTOR_ATTRACTIVE = 1.5;	//2.2// коэффициент силы приятжения
	public static  double FORCE_FACTOR_REPULSIVE = 1.0;	// коэффициент силы отталкивания
	public static  int TIME_FACTOR = 10;					// скорость рассчета относительно реалтайма
	public static  int SLEEP_TIME = 10;					// время сна потока перед апдейтом
	public static  long DELTA_TIME = 10000 * SLEEP_TIME / TIME_FACTOR;
	
	private static boolean bProcessing;
	private boolean altCalc;
	private boolean bScatter;
	private boolean bScatterLast;
	private long lastTime;
	private long delta;
	
	protected Graph graph;
	protected static ArrayList<Node> group;
	
	public GraphCalculate() {
	}
	
	public GraphCalculate(Graph graph) {
		this.graph = graph;
		bProcessing = true;
		runCalculate();
	}
	
	protected final void runCalculate() {
		lastTime = System.currentTimeMillis();
		calculateGraph();
	}
	
	protected void calculateGraph() {
		updateService();
		log("calculation for graph: " + graph.getID() + " is started!");
		boolean offsetAvailable = false;
		do {
//			try {
//				Thread.sleep(SLEEP_TIME / (TIME_FACTOR*10));
//			} catch (InterruptedException ex) {
//				return;
//			}
			if(!altCalc) {
				offsetAvailable = calculation(offsetAvailable);
			} else {
				offsetAvailable = altCalculation(offsetAvailable);
			}
			
			if(delta > 5000 && !bScatter) {
//				offsetAvailable = false;
				bScatter = true;
				scatterNodes();
			} else if(!bScatterLast && delta > 8000) {
				bScatterLast = true;
				group = scatterNodes();
				altCalc = true;
				log("graph is complex!");
				log("alternative calculation is started!");
			}
			
			// нужно ли производить смещение
			if(!offsetAvailable) {
				bProcessing = false;
				delta = System.currentTimeMillis() - lastTime;
				log("time elapsed: " + delta + "ms");
			} else {	
				for(int i = 0 ; i < graph.getSizeGraph(); i++) {
					// выполняем смещение в текущем такте
					if(graph.getNode(i).isNullOffset()) {
						graph.getNode(i).setActive(false);
					} else {
						graph.getNode(i).setActive(true);
					}
					graph.getNode(i).performOffset();
				}
				offsetAvailable = false;
			}
			delta = System.currentTimeMillis() - lastTime;
			if(delta > DELTA_TIME && !altCalc) {
				log("time's up! abort calculation!");
				bProcessing = false;
			} else if(delta > DELTA_TIME+5000 && altCalc) {
				log("calculation is completed!");
				log("time elapsed: " + delta + "ms");
				bProcessing = false;
			}
			graph.update();
		} while(bProcessing);
		updateService();
	}

	
	
	
////////////////////////////////////////////////////////////////////////////////
	/**
	 * 
	*/
	private boolean calculation(boolean offsetAvailable) {
		for (int i = 0; i < graph.getSizeGraph(); i++) {
			Node nodeToMove = graph.getNode(i);
			for(int j = 0; j < graph.getSizeGraph(); j++) {
				if(i != j) {
					Node nodeToCalc = graph.getNode(j);
					// distanceBetweenNodes
					double dbn = getDistance(nodeToMove, nodeToCalc);
					// проверяем ноды на наличие ребра
					if (nodeToMove.checkEdge(nodeToCalc)) {
//					log("node: " + nodeToMove.getID() + " | node: "
//							+ nodeToCalc.getID() + " | have an edge");
						if (dbn > MIN_DISTANCE_ATTRACTIVE) {
							offsetAvailable = true;
							nodeToMove.setOffset(calcDistAttractive(nodeToMove, nodeToCalc, dbn));
						} else if (dbn < MAX_DISTANCE_REPULSIVE) {
							offsetAvailable = true;
							nodeToMove.setOffset(calcDistRepulsive(nodeToMove, nodeToCalc, dbn));
						} else if (dbn >= MAX_DISTANCE_REPULSIVE
								&& dbn <= MIN_DISTANCE_ATTRACTIVE) {
//						log("node: " + nodeToMove.getID() + " |  don't need to move");
						}
					} else {
						if(dbn < MIN_DISTANCE_WITHOUT_EDGE) {
							offsetAvailable = true;
							nodeToMove.setOffset(calcDistWithoutEdge(nodeToMove, nodeToCalc, dbn));
//						} else if(dbn > MIN_DISTANCE_WITHOUT_EDGE + 100) {
//							offsetAvailable = true;
//							nodeToMove.setOffset(calcDistWithoutEdgeAt(nodeToMove, nodeToCalc, dbn));
						}
					}
				}
			}
		}
		return offsetAvailable;
	}
	
////////////////////////////////////////////////////////////////////////////////
	/**
	 * 
	*/	
	private boolean altCalculation(boolean offsetAvailable) {
		for (int i = 0; i < group.size(); i++) {
			Node nodeToMove = group.get(i);
			for (int j = 0; j < group.size(); j++) {
				if (i != j) {
					Node nodeToCalc = group.get(j);
					// distanceBetweenNodes
					double dbn = getDistance(nodeToMove, nodeToCalc);
					// проверяем ноды на наличие ребра
					if (nodeToMove.checkEdge(nodeToCalc)) {
//					log("node: " + nodeToMove.getID() + " | node: "
//							+ nodeToCalc.getID() + " | have an edge");
						if (dbn > MIN_DISTANCE_ATTRACTIVE + 20) {
							offsetAvailable = true;
							nodeToMove.setOffset(calcDistAttractiveALT(nodeToMove, nodeToCalc, dbn));
						} else if (dbn < MAX_DISTANCE_REPULSIVE + 20) {
							offsetAvailable = true;
							nodeToMove.setOffset(calcDistRepulsiveALT(nodeToMove, nodeToCalc, dbn));
						} else if (dbn >= MAX_DISTANCE_REPULSIVE + 20
								&& dbn <= MIN_DISTANCE_ATTRACTIVE + 20) {
//						log("node: " + nodeToMove.getID() + " |  don't need to move");
						}
					} else {
						if(dbn < MIN_DISTANCE_WITHOUT_EDGE) {
							offsetAvailable = true;
							nodeToMove.setOffset(calcDistWithoutEdgeALT(nodeToMove, nodeToCalc, dbn));
						}
					}
				}
			}
		}
		return offsetAvailable;
	}
	
////////////////////////////////////////////////////////////////////////////////
	
	protected ArrayList<Node> scatterNodes() {
		ArrayList<Node> currentGroup = new ArrayList<>();
		Random rnd = new Random();
		int value = rnd.nextInt(2);
		if(value == 1) {
			value = 1;
		} else value = -1;
		log("scatter nodes!");
		for(int i = 0; i < graph.getSizeGraph(); i++) {
			if(graph.getNode(i).getOffsetX() > 1 ||
					graph.getNode(i).getOffsetY() > 1 ) {
				graph.getNode(i).setOffset(rnd.nextInt(400)*value, rnd.nextInt(400)*value);
				currentGroup.add(graph.getNode(i));
			}
		}
		return currentGroup;
	}
	
	// рассчитывает величину смещения для притяжения
	// calculateDistanceAttractive
	protected Offset calcDistAttractive(Node nodeToMove, Node nodeToCalc, double dbn) {
		Offset offset = new  Offset();	
		offset.offsetX = (nodeToCalc.getCoordX() - nodeToMove.getCoordX()) / dbn/FORCE_FACTOR_REPULSIVE/4;
		offset.offsetY = (nodeToCalc.getCoordY() - nodeToMove.getCoordY()) / dbn/FORCE_FACTOR_REPULSIVE/4;
		return offset;
	}
	// рассчитывает величину смещения для отталкивания
	// calculateDistanceRepulsive
	protected Offset calcDistRepulsive(Node nodeToMove, Node nodeToCalc, double dbn) {
		Offset offset = new Offset();
		offset.offsetX = (nodeToCalc.getCoordX() - nodeToMove.getCoordX()) / dbn*FORCE_FACTOR_ATTRACTIVE/2;
		offset.offsetY = (nodeToCalc.getCoordY() - nodeToMove.getCoordY()) / dbn*FORCE_FACTOR_ATTRACTIVE/2;
		offset.reverseOffset();
		return offset;
	}
	// рассчитывает величину смещения для отталкивания несвязанных нодов
	// calculateDistanceRepulsive
	protected Offset calcDistWithoutEdge(Node nodeToMove, Node nodeToCalc, double dbn) {
		Offset offset = new Offset();
		offset.offsetX = (nodeToCalc.getCoordX() - nodeToMove.getCoordX()) / dbn/FORCE_FACTOR_REPULSIVE;
		offset.offsetY = (nodeToCalc.getCoordY() - nodeToMove.getCoordY()) / dbn/FORCE_FACTOR_REPULSIVE;
		offset.reverseOffset();
		return offset;
	}
	// рассчитывает величину смещения для отталкивания несвязанных нодов
	// calculateDistanceRepulsive
	protected Offset calcDistWithoutEdgeAt(Node nodeToMove, Node nodeToCalc, double dbn) {
		Offset offset = new Offset();
		offset.offsetX = (nodeToCalc.getCoordX() - nodeToMove.getCoordX()) / dbn/FORCE_FACTOR_REPULSIVE/8;
		offset.offsetY = (nodeToCalc.getCoordY() - nodeToMove.getCoordY()) / dbn/FORCE_FACTOR_REPULSIVE/8;
		return offset;
	}
	
	// ALT
	protected Offset calcDistAttractiveALT(Node nodeToMove, Node nodeToCalc, double dbn) {
		Offset offset = new  Offset();	
		offset.offsetX = (nodeToCalc.getCoordX() - nodeToMove.getCoordX()) / dbn/FORCE_FACTOR_ATTRACTIVE/4;
		offset.offsetY = (nodeToCalc.getCoordY() - nodeToMove.getCoordY()) / dbn/FORCE_FACTOR_ATTRACTIVE/4;
		return offset;
	}
	protected Offset calcDistRepulsiveALT(Node nodeToMove, Node nodeToCalc, double dbn) {
		Offset offset = new Offset();
		offset.offsetX = (nodeToCalc.getCoordX() - nodeToMove.getCoordX()) / dbn/FORCE_FACTOR_REPULSIVE/3;
		offset.offsetY = (nodeToCalc.getCoordY() - nodeToMove.getCoordY()) / dbn/FORCE_FACTOR_REPULSIVE/3;
		offset.reverseOffset();
		return offset;
	}
	protected Offset calcDistWithoutEdgeALT(Node nodeToMove, Node nodeToCalc, double dbn) {
		Offset offset = new Offset();
		offset.offsetX = (nodeToCalc.getCoordX() - nodeToMove.getCoordX()) / dbn/FORCE_FACTOR_REPULSIVE/1;
		offset.offsetY = (nodeToCalc.getCoordY() - nodeToMove.getCoordY()) / dbn/FORCE_FACTOR_REPULSIVE/1;
		offset.reverseOffset();
		return offset;
	}
	
	public static boolean getStateProcessing() {
		return bProcessing;
	}
	
	protected void updateService() {
		if(Service.getServiceState()) {
			Storage.getService().updateStateService();
		}
	}
	
	protected void log(Object obj) {
		if(Service.getServiceState()) {
			Service.log(obj);
		}
	}
	
	protected double getDistance(Node n1, Node n2) {
		return Math.sqrt((double)(n2.getCoordX() - n1.getCoordX())*(n2.getCoordX() - n1.getCoordX())
				+ (n2.getCoordY() - n1.getCoordY())*(n2.getCoordY() - n1.getCoordY()));
	}
}
